/****************************************************
	文件：ResScheduler.cs
	作者：HuskyT
	邮箱：1005240602@qq.com
	日期：2020/12/20 18:37:17
	功能：资源加载调度器
*****************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using HTFW.Core;
using HTFW.Logger;
using UnityEngine;
using HTFW.Utility.Global;
using IDisposable = HTFW.Core.IDisposable;

namespace HTFW.Res
{
    public class ResScheduler : Singleton<ResScheduler>, IDisposable
    {
        /// <summary>
        /// 设定每帧最大加载时间：16毫秒，满负荷加载会导致60帧 => 30帧，可以接受
        /// 60帧：每帧 16.67 毫秒
        /// 30帧：每帧 33.33 毫秒
        /// </summary>
        public const long MAX_SPEND_TIME_PER_FRAME = 160000;

        private long mLastLoadTime;

        Queue<ResData> mLowABQue = new Queue<ResData>();
        Queue<ResData> mMidABQue = new Queue<ResData>();
        Queue<ResData> mHighABQue = new Queue<ResData>();

        Queue<ResData> mLowAssetQue = new Queue<ResData>();
        Queue<ResData> mMidAssetQue = new Queue<ResData>();
        Queue<ResData> mHighAssetQue = new Queue<ResData>();

        IRoutineHandle mAsyncLoadCoro; //异步加载的协程

        public void Init()
        {
            mAsyncLoadCoro = CoroutineService.Instance.Start(AsyncLoadCorou());
        }

        public void Dispose()
        {
            mAsyncLoadCoro.Stop();
            mLowABQue.Clear();
            mMidABQue.Clear();
            mHighABQue.Clear();
            mLowAssetQue.Clear();
            mMidAssetQue.Clear();
            mHighAssetQue.Clear();
        }

        public void AddResCommand(ResData resData)
        {
            if (resData.ResType == ResType.Asset)
            {
                switch (resData.Priority)
                {
                    case ResTaskPriority.None:
                        CoroutineService.Instance.Start(LoadAssetAsync(resData));
                        break;
                    case ResTaskPriority.Low:
                        mLowAssetQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.Mid:
                        mMidAssetQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.High:
                        mHighAssetQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.Immediately:
                        LoadAssetSync(resData);
                        break;
                }
            }
            else if (resData.ResType == ResType.AssetBundle)
            {
                switch (resData.Priority)
                {
                    case ResTaskPriority.None:
                        CoroutineService.Instance.Start(LoadABAsync(resData));
                        break;
                    case ResTaskPriority.Low:
                        mLowABQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.Mid:
                        mMidABQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.High:
                        mHighABQue.Enqueue(resData);
                        break;
                    case ResTaskPriority.Immediately:
                        LoadABSync(resData);
                        break;
                }
            }
        }

        IEnumerator AsyncLoadCorou()
        {
            mLastLoadTime = DateTime.Now.Ticks;
            while (true)
            {
                if (mHighAssetQue.Count > 0)
                {
                    ResData assetRes = mHighAssetQue.Dequeue();
                    yield return LoadAssetAsync(assetRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("High Asset Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }
                else if (mHighABQue.Count > 0)
                {
                    ResData abRes = mHighABQue.Dequeue();
                    //yield return LoadABAsync(abRes);
                    LoadABSync(abRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("High AB Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }
                else if (mMidAssetQue.Count > 0)
                {
                    ResData assetRes = mMidAssetQue.Dequeue();
                    yield return LoadAssetAsync(assetRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("Mid Asset Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }
                else if (mMidABQue.Count > 0)
                {
                    ResData abRes = mMidABQue.Dequeue();
                    //yield return LoadABAsync(abRes);
                    LoadABSync(abRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("Mid AB Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }
                else if (mLowAssetQue.Count > 0)
                {
                    ResData assetRes = mLowAssetQue.Dequeue();
                    yield return LoadAssetAsync(assetRes);
                    //LoadAssetSync(assetRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("Low Asset Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }
                else if (mLowABQue.Count > 0)
                {
                    ResData abRes = mLowABQue.Dequeue();
                    //yield return LoadABAsync(abRes);
                    LoadABSync(abRes);
                    if (DateTime.Now.Ticks - mLastLoadTime > MAX_SPEND_TIME_PER_FRAME)
                    {
                        HLog.Warning("Low AB Que");
                        yield return null;
                        mLastLoadTime = DateTime.Now.Ticks;
                        continue;
                    }
                    else
                        continue;
                }

                yield return null;
                mLastLoadTime = DateTime.Now.Ticks;
            }
        }

        IEnumerator LoadABAsync(ResData abRes)
        {
            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(ResUtil.GetABFullPath(abRes.ABName));
            yield return request;
            if (request.isDone)
            {
                abRes.AB = request.assetBundle;
                abRes.OnAsyncLoadDone?.Invoke();
            }
        }

        IEnumerator LoadAssetAsync(ResData assetRes)
        {
            AssetBundleRequest request;
            if (assetRes.IsSprite)
                request = assetRes.AB.LoadAssetAsync<Sprite>(assetRes.AssetName);
            else
                request = assetRes.AB.LoadAssetAsync(assetRes.AssetName);

            yield return request;
            if (request.isDone)
            {
                assetRes.Obj = request.asset;
                assetRes.OnAsyncLoadDone?.Invoke();
            }
        }

        void LoadABSync(ResData abRes)
        {
            abRes.AB = AssetBundle.LoadFromFile(ResUtil.GetABFullPath(abRes.ABName));
            abRes.OnAsyncLoadDone?.Invoke();
        }

        void LoadAssetSync(ResData assetRes)
        {
            if (assetRes.IsSprite)
                assetRes.Obj = assetRes.AB.LoadAsset<Sprite>(assetRes.AssetName);
            else
                assetRes.Obj = assetRes.AB.LoadAsset(assetRes.AssetName);
            assetRes.OnAsyncLoadDone?.Invoke();
        }
    }
}